package ar.fiuba.logger.configfile;

import javax.naming.ConfigurationException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.Element;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;

import ar.fiuba.logger.LogConfig;
import ar.fiuba.logger.LogConfigReader;
import ar.fiuba.logger.LogFormatterInterface;
import ar.fiuba.logger.LogLevel;
import ar.fiuba.logger.filter.LogLevelLessThanOrEqualToThresholdFilter;
import ar.fiuba.logger.filter.MessageMatchesRegularExpressionFilter;
import ar.fiuba.logger.formatter.LogTextFormatter;
import ar.fiuba.logger.reporter.ConsoleReporter;
import ar.fiuba.logger.reporter.FileReporter;

/**
 * The Class LogConfigXmlFile.
 * 
 * This class implements the log configuration file. It parses configuration details from a
 * properties files.
 */
public class LogConfigXmlFile implements LogConfigReader {

  /** The filename. */
  protected String filename;

  /**
   * Instantiates a new log configuration file.
   * 
   * @param filename the filename
   */
  public LogConfigXmlFile(String filename) {
    this.filename = filename;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    LogConfigXmlFile other = (LogConfigXmlFile) obj;
    if (filename == null) {
      if (other.filename != null)
        return false;
    } else if (!filename.equals(other.filename))
      return false;
    return true;
  }

  /**
   * Reads the content of an xml Element node.
   *
   * @param element the element
   * @param tagName the tag
   * @return content
   */
  private String getElementTextContent(Element element, String tagName) {
    return element.getElementsByTagName(tagName).item(0).getTextContent();
  }

  /* (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((filename == null) ? 0 : filename.hashCode());
    return result;
  }

  /**
   * Parses the config.
   *
   * @param doc the doc
   * @return the log config
   * @throws ConfigurationException the configuration exception
   * @throws FileNotFoundException the file not found exception
   * @throws UnsupportedEncodingException the unsupported encoding exception
   * @throws DOMException the DOM exception
   */
  private LogConfig parseConfig(Document doc) throws ConfigurationException, FileNotFoundException,
      UnsupportedEncodingException, DOMException {
    LogConfig logConfig = new LogConfig();

    logConfig.reporters.clear();
    logConfig.filters.clear();

    /*
     * optional, but recommended read this -
     * http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work
     */
    doc.getDocumentElement().normalize();

    NodeList nList = doc.getElementsByTagName("log_config");

    if (nList.getLength() != 1)
      throw new ConfigurationException("No tag log_config found");

    Node nNode = nList.item(0);

    if (nNode.getNodeType() != Node.ELEMENT_NODE)
      throw new ConfigurationException("Wrong node type");

    Element configElement = (Element) nNode;

    logConfig.separator = this.getElementTextContent(configElement, "separator");
    logConfig.format = this.getElementTextContent(configElement, "format");
    logConfig.threshold =
        LogLevel.valueOf(this.getElementTextContent(configElement, "log_level").toUpperCase());
    logConfig.regex = this.getElementTextContent(configElement, "regex");

    logConfig.filters.add(new LogLevelLessThanOrEqualToThresholdFilter(logConfig.threshold));

    /*
     * For the moment all FileReporters and the ConsoleReporter use the same format configuration,
     * this may be changed in the future.
     */
    LogFormatterInterface formatter = new LogTextFormatter(logConfig.format, logConfig.separator);


    // Find console loggers
    NodeList console;

    console = configElement.getElementsByTagName("console");

    if (console != null) {
      if (console.item(0).getTextContent().compareToIgnoreCase("ON") == 0)
        logConfig.reporters.add(new ConsoleReporter(formatter));
    }

    // Find file reporters
    nList = doc.getElementsByTagName("log_file");

    for (int i = 0; i < nList.getLength(); i++) {
      configElement = (Element) nList.item(i);
      logConfig.reporters.add(new FileReporter(configElement.getTextContent(), formatter));
    }

    if (logConfig.regex != null && !logConfig.regex.isEmpty()) {
      logConfig.filters.add(new MessageMatchesRegularExpressionFilter(logConfig.regex));
    }

    return logConfig;
  }

  /**
   * Read.
   * 
   * @return the log configuration
   */
  public LogConfig read() {
    LogConfig config = new LogConfig();

    try {
      File fXmlFile = new File(filename);
      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
      Document doc = dBuilder.parse(fXmlFile);
      config = this.parseConfig(doc);

    } catch (Exception ex) {
      System.err.print("An error has ocurred while trying to read the config file.");
      ex.printStackTrace();

      /*
       * In case that an error occurs while reading the config file, the default config is used
       * instead.
       */
      config = new LogConfig();
    }

    return config;
  }

}
